package com.google.code.swingonfire.utility;

import java.math.BigDecimal;
import java.text.Collator;
import java.util.Arrays;
import java.util.Comparator;

public class Utils
{

    public static final Collator DICTIONARY_COLLATOR;

    public static final Comparator<String> DICTIONARY_COMPARATOR = new Comparator<String>()
    {

        @Override
        public int compare(String left, String right)
        {
            return dictionaryCompare(left, right);
        }

    };

    static
    {
        DICTIONARY_COLLATOR = Collator.getInstance();

        DICTIONARY_COLLATOR.setStrength(Collator.PRIMARY);
        DICTIONARY_COLLATOR.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
    }

    private Utils()
    {
        super();
    }

    public static boolean equals(Object a, Object b)
    {
        if (a == b)
        {
            return true;
        }

        if ((a == null) && (b != null))
        {
            return false;
        }

        if ((a != null) && (b == null))
        {
            return false;
        }

        if ((a instanceof boolean[]) && (b instanceof boolean[]))
        {
            return Arrays.equals((boolean[]) a, (boolean[]) b);
        }

        if ((a instanceof byte[]) && (b instanceof byte[]))
        {
            return Arrays.equals((byte[]) a, (byte[]) b);
        }

        if ((a instanceof short[]) && (b instanceof short[]))
        {
            return Arrays.equals((short[]) a, (short[]) b);
        }

        if ((a instanceof int[]) && (b instanceof int[]))
        {
            return Arrays.equals((int[]) a, (int[]) b);
        }

        if ((a instanceof long[]) && (b instanceof long[]))
        {
            return Arrays.equals((long[]) a, (long[]) b);
        }

        if ((a instanceof float[]) && (b instanceof float[]))
        {
            return Arrays.equals((float[]) a, (float[]) b);
        }

        if ((a instanceof double[]) && (b instanceof double[]))
        {
            return Arrays.equals((double[]) a, (double[]) b);
        }

        if ((a instanceof char[]) && (b instanceof char[]))
        {
            return Arrays.equals((char[]) a, (char[]) b);
        }

        if ((a instanceof Object[]) && (b instanceof Object[]))
        {
            Object[] arrayA = (Object[]) a;
            Object[] arrayB = (Object[]) b;

            if (arrayA.length != arrayB.length)
            {
                return false;
            }

            for (int i = 0; i < arrayA.length; i += 1)
            {
                if (!equals(arrayA[i], arrayB[i]))
                {
                    return false;
                }
            }

            return true;
        }

        if ((a instanceof BigDecimal) && (b instanceof BigDecimal))
        {
            return ((BigDecimal) a).compareTo((BigDecimal) b) == 0;
        }

        return a.equals(b);
    }

    public static double bound(double minimum, double value, double maximum)
    {
        if (value < minimum)
        {
            return minimum;
        }

        if (value > maximum)
        {
            return maximum;
        }

        return value;
    }

    public static double round(double value, double toNearest, double offset)
    {
        return round(value - offset, toNearest) + offset;
    }

    public static double round(double value, double toNearest)
    {
        if (toNearest == 0)
        {
            return value;
        }

        double toNearestInv = 1 / toNearest;

        return Math.round(value * toNearestInv) / toNearestInv;
    }

    public static double clamp(double minimum, double value, double maximum)
    {
        double diff = maximum - minimum;

        if (diff > 0)
        {
            while (value < minimum)
            {
                value += diff;
            }

            while (value >= maximum)
            {
                value -= diff;
            }
        }
        else if (diff < 0)
        {
            while (value <= maximum)
            {
                value -= diff;
            }

            while (value > minimum)
            {
                value += diff;
            }
        }
        else
        {
            throw new IllegalArgumentException("minimum == maximum");
        }

        return value;
    }

    public static int clamp(int minimum, int value, int maximum)
    {
        int diff = maximum - minimum;

        if (diff > 0)
        {
            while (value < minimum)
            {
                value += diff;
            }

            while (value >= maximum)
            {
                value -= diff;
            }
        }
        else if (diff < 0)
        {
            while (value <= maximum)
            {
                value -= diff;
            }

            while (value > minimum)
            {
                value += diff;
            }
        }
        else
        {
            throw new IllegalArgumentException("minimum == maximum");
        }

        return value;
    }

    /**
     * Compares the two objects. If one of the objects is null, it will always be greater than the other object. If both
     * objects are null, they are equal.
     * 
     * @param <TYPE> the type of the object
     * @param left the first object
     * @param right the second object
     * @return the result of the compare function
     */
    public static <TYPE extends Comparable<TYPE>> int compare(final TYPE left, final TYPE right)
    {
        if (left == null)
        {
            if (right != null)
            {
                return 1;
            }
        }
        else
        {
            if (right != null)
            {
                return left.compareTo(right);
            }

            return -1;
        }

        return 0;
    }

    /**
     * Compares the two objects. If one of the objects is null, it will always be greater than the other object. If both
     * objects are null, they are equal. Uses the comparator to compare the objects.
     * 
     * @param <TYPE> the type of the object
     * @param comparator the comparator to be used
     * @param left the first object
     * @param right the second object
     * @return the result of the compare function
     */
    public static <TYPE> int compare(final Comparator<TYPE> comparator, final TYPE left, final TYPE right)
    {
        if (left == null)
        {
            if (right != null)
            {
                return 1;
            }
        }
        else
        {
            if (right != null)
            {
                return comparator.compare(left, right);
            }

            return -1;
        }

        return 0;
    }

    /**
     * Compares the strings using a dictionary collator. If one of the objects is null, it will always be greater than
     * the other object. If both objects are null, they are equal.
     * 
     * @param left the first string
     * @param right the second string
     * @return the result of the compare function
     */
    public static int dictionaryCompare(final String left, final String right)
    {
        return compare(DICTIONARY_COLLATOR, left, right);
    }

    /**
     * Nears to the correct fractions. The sum of the initial fractions must not be 1, it will be correct. The sum of
     * the results will be 1 if the minimums allow this.
     * 
     * @param initialFractions the initial fractions to play with, the sum must be 1
     * @param minimums the minimum values
     * @param maximums the maximum values
     * @return the neared fractions
     */
    public static double[] nearFractions(double[] initialFractions, double[] minimums, double[] maximums)
    {
        double[] results = new double[initialFractions.length];
        double[] fractions = Arrays.copyOf(initialFractions, initialFractions.length);
        double sum = 0;

        // calculate the sum of the fractions
        for (double fraction : fractions)
        {
            sum += fraction;
        }

        double multiplier = 1 / sum;

        // correct the fractions, so that the sum is 1
        for (int i = 0; i < fractions.length; i += 1)
        {
            fractions[i] *= multiplier;
        }

        double remaining = 1;
        sum = 0;

        // distribute the minimum
        for (int i = 0; i < fractions.length; i += 1)
        {
            if (minimums[i] >= 0)
            {
                results[i] = minimums[i];
                remaining -= minimums[i];

                if (fractions[i] > 0)
                {
                    fractions[i] = Math.max(fractions[i] - minimums[i], 0);
                }
            }

            sum += fractions[i];
        }

        if (remaining <= 0)
        {
            return results;
        }

        int iterations = 1;

        while (iterations <= 8)
        {
            multiplier = 1 / sum;

            // correct the fractions, so that the sum is 1
            for (int i = 0; i < fractions.length; i += 1)
            {
                fractions[i] *= multiplier;
            }

            double distributable = remaining;
            sum = 0;

            // lock the maximum
            for (int i = 0; i < fractions.length; i += 1)
            {
                double d = distributable * fractions[i];

                if ((results[i] + d) > maximums[i])
                {
                    d = maximums[i] - results[i];
                    fractions[i] = 0;
                }

                remaining -= d;
                results[i] += d;
                sum += fractions[i];
            }

            if (remaining <= 0)
            {
                return results;
            }

            if (sum <= 0)
            {
                return results;
            }

            iterations += 1;
        }

        return results;
    }

    public static String toString(String format, float[] values)
    {
        StringBuilder builder = new StringBuilder("[");

        for (int i = 0; i < values.length; i += 1)
        {
            if (i > 0)
            {
                builder.append(" | ");
            }

            builder.append(String.format(format, values[i]));
        }

        builder.append("]");

        return builder.toString();
    }

    public static String toString(String format, double[] values)
    {
        StringBuilder builder = new StringBuilder("[");

        for (int i = 0; i < values.length; i += 1)
        {
            if (i > 0)
            {
                builder.append(" | ");
            }

            builder.append(String.format(format, values[i]));
        }

        builder.append("]");

        return builder.toString();
    }

}
